/*
 * CoordinateList
 * 
 * initial implementation : 29.8.2007
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Petra.Gis.Geometry;


namespace Petra.Gis.Geometry.Support
{
  /// <summary>
  /// CoordinateList - binary storage of geometry spatial coordinates
  /// created for fast access to geometry coordinates w/out copy of source coordinates array
  /// contains byte[] array (buffer) with all coordinates for series of point's
  /// unsafe class, uses poiners
  /// </summary>
  public unsafe class CoordinateList : IList<ICoordinate>
  {
    protected bool _isEmpty;
    protected Dimension _dimension;
    protected int _numCoords; //number of coordinates in array

    private InternalCoordinate _currentPoint;
    private InternalCoordinate[] _internalCoords;

    internal protected const int sizeOfDouble = sizeof(double); //coordinate must be always 8 bytes double
    internal protected int coordSize;  //size of all ordinates of one point, differs for 2d = 16 and 3d = 24
    internal protected byte[] _buffer;
    internal protected int beginOfCoords; //begin of coordinates in byte array (byte offset)

    /*public CoordinateList()
    {
      _isEmpty = true;
    }
    */



    /// <summary>
    /// public constructor for coordList given by number of points and dimension
    /// </summary>
    /// <param name="NumCoords">number of coordinates to store in this</param>
    /// <param name="dim">dimension of coordinate</param>
    public CoordinateList(int NumCoords, Dimension dim )
    {
      coordSize = sizeOfDouble * (int)dim;

      byte[] tmpArray = new byte[NumCoords * coordSize];

      _buffer = tmpArray;
      _dimension = dim;
      _numCoords = NumCoords;

      //indexOfCurrPoint = 0;
      _currentPoint = new InternalCoordinate(this, 0);
      _isEmpty = false;
      initInternalCoords();
    }




    /// <summary>
    /// special constructor for variable amout of ordinate
    /// </summary>
    /// <param name="dim">dimension</param>
    /// <param name="dCoords">list of ordinates, num of ordinates must be multiply of dimension</param>
    public CoordinateList(Dimension dim, params double[] dCoords)
    {
      //check proper input array size
      if (dCoords.Length % (int)dim != 0)
        throw new ArgumentException("invalid size of input array");

      coordSize = sizeOfDouble * (int)dimension;

      int lBytesToCpy = dCoords.Length * coordSize;
      _numCoords = dCoords.Length / (int)dim;
      byte[] tmpArray = new byte[lBytesToCpy];
      System.Buffer.BlockCopy(dCoords, 0, tmpArray, 0, lBytesToCpy);

      _buffer = tmpArray;
      _dimension = dim;

      //indexOfCurrPoint = 0;
      _currentPoint = new InternalCoordinate(this,0);
      _isEmpty = false;
      initInternalCoords();
    }




    //public CoordinateList(BinaryReader mBReader, int BeginOfCoords, int NumCoords, Dimension dim)
    //{
    //  coordSize = sizeOfDouble * (int)dim;
    //  if (mBReader.BaseStream.Length < ((coordSize * NumCoords) + BeginOfCoords))
    //    throw new ArgumentException("supplied array is to small for defined coordlist");

    //  _buffer = ((MemoryStream)mBReader.BaseStream).GetBuffer();
    //  beginOfCoords = BeginOfCoords;
    //  _dimension = dim;
    //  _numCoords = NumCoords;

    //  //advance stream position !!
    //  mBReader.BaseStream.Position = BeginOfCoords + (NumCoords * coordSize);

    //  //indexOfCurrPoint = 0;
    //  _currentPoint = new InternalCoordinate(this, 0);
    //  _isEmpty = false;
    //  initInternalCoords();
    //}




    /// <summary>
    /// creates this from existing binary array from given start position in this array and number of coordinates
    /// </summary>
    /// <param name="vData">reference to input array with coordinates</param>
    /// <param name="BeginOfCoords">position of first coordinate in array</param>
    /// <param name="NumCoords">number of coordinates to read</param>
    /// <param name="dim">coordinate dimension</param>
    public CoordinateList(byte[] vData, int BeginOfCoords, int NumCoords, Dimension dim)
    {
      coordSize = sizeOfDouble * (int)dim;
      if (vData.Length < ((coordSize * NumCoords) + BeginOfCoords))
        throw new ArgumentException("supplied array is to small for defined coordlist");

      _buffer = vData;
      beginOfCoords = BeginOfCoords;
      _dimension = dim;
      _numCoords = NumCoords;

      //indexOfCurrPoint = 0;
      _currentPoint = new InternalCoordinate(this, 0);
      _isEmpty = false;
      initInternalCoords();
    }




    public bool isEmpty {
      get { return _isEmpty; }
    }




    private void initInternalCoords()
    {
      //internalCoords.GetEnumerator();
      _internalCoords = new InternalCoordinate[_numCoords];
    }




    /*
    // this enumerator returns new instance of Coordinate for all Coordinates in byte[] array 
    public IEnumerator<ICoordinate> GetEnumerator()
    {
      int i = 0;
      while (i < numCoords)
      {
        yield return (_internalCoords[i] == null ? _internalCoords[i] = new InternalCoordinate(this, i) : _internalCoords[i]);
        i++;
      }
    }
    */





    /// <summary>
    /// this enumerator returns still same instance of Coordinate for all items in collection !!
    /// to get unique instance for each point in CoordinateList, use this[] indexer
    /// </summary>
    /// <returns>returns<c>Coordinate</c></returns>
    public IEnumerator<ICoordinate> GetEnumerator()
    {
      int i = 0;
      InternalCoordinate tmpCoord = new InternalCoordinate(this, 0);
      while (i < _numCoords)
      {
        _currentPoint.index = i;
        yield return (Coordinate)_currentPoint;
        i++;
      }
    }



    IEnumerator IEnumerable.GetEnumerator()
    {
      return GetEnumerator();
    }



    public void Add(ICoordinate oGeom)
    { 
      throw new NotImplementedException();
    }




    public int IndexOf(ICoordinate Point)
    {
      throw new NotImplementedException();
    }



    public void Clear()
    {
      throw new NotImplementedException();
    }




    public void Insert(int Index, ICoordinate oGeom)
    {
      throw new NotImplementedException();
    }




    //public bool Remove(T oGeom)
    public bool Remove(ICoordinate oGeom)
    {
      throw new NotImplementedException();
    }



    public void RemoveAt(int Index)
    {
      throw new NotImplementedException();
    }




    public ICoordinate this[int i] //indexer
    {
      get
      {
        if (i < 0 || i >= _numCoords)
          throw new ArgumentOutOfRangeException("", "index");

        //if (_internalCoords == null)
        //  initInternalCoords();

       //return getCoord(i);
        return (_internalCoords[i] == null ? _internalCoords[i] = new InternalCoordinate(this, i) : _internalCoords[i]);
      }
      set
      {
        _currentPoint.index = i;
        _currentPoint.X = value.X;
        _currentPoint.Y = value.Y;
        _currentPoint.Z = value.Z;
      }
    }




    public InternalCoordinate startPoint
    {
      get { return (_internalCoords[0] == null ? _internalCoords[0] = new InternalCoordinate(this, 0) : _internalCoords[0]); }
    }




    public InternalCoordinate endPoint
    {
      get { int i = _numCoords - 1;
        return (_internalCoords[i] == null ? _internalCoords[i] = new InternalCoordinate(this, i) : _internalCoords[i]); 
      }
    }




    //carefull !!! compare ordinates, not objects !!!
    public bool Contains(ICoordinate Point)
    {
      int i = 0;
      while (i < _numCoords)
      {
        if(Point.Equals(this[i],dimension))
          return true;
      }
      return false;
    }



    public void CopyTo(ICoordinate[] Points, int Index)
    {
      throw new NotImplementedException();
    }



    public int Count
    {
      get { return _numCoords; }
    }



    //IsReadOnly change to false when point edit finished
    public bool IsReadOnly
    {
      get { return true; }
    }

    
    
    public Dimension dimension
    {
      get { return _dimension; }
    }



    /// <summary>
    /// returns current buffer with coordinates in raw byte[] format
    /// </summary>
    /// <returns>array of bytes</returns>
    public byte[] getBuffer()
    {
      return _buffer;
    }




    public int position
    {
      get { return beginOfCoords; }
    }




    /// <summary>
    /// converts current coordList to byte[] by given dimension.
    /// if current array is shared between multiple geometry object this call causes
    /// than current geometry creates its own array to store coordinates (allocates new array)
    /// </summary>
    /// <param name="destDimension">target dimension</param>
    /// <returns>array of bytes</returns>
    public byte[] ToArray(Dimension destDimension)
    {
      byte[] tmpArray;

      if (destDimension != dimension)
        if (destDimension == Dimension.dim3D)
          tmpArray = convert_2Dto3D(0); // default Z = 0
        else
          tmpArray = convert_3Dto2D();
      else
      {
        //todo:not implemented when point add and point remove inside array or different array size
        //then num of points
        if (beginOfCoords > 0 || (_buffer.Length != (coordSize * _numCoords)))
        {
          //throw new NotImplementedException("ToArray not implemented for non continuous array");
          //just compress the coords array
          tmpArray = new byte[(_numCoords * (sizeOfDouble * (int)dimension))];
          System.Buffer.BlockCopy(_buffer, beginOfCoords, tmpArray, 0, (_numCoords * (sizeOfDouble * (int)dimension)));
        }
        else
          tmpArray = _buffer;
      }

       return tmpArray;

    }



     /*
     * envelope array:
     * 5 points * 2 ordinates per point
     * point 1 and 5 are same value
     * (5)(1)-----------------------(2)
     *  |                            |
     *  |                            |
     *  |                            |
     *  |                            |
     *  |                            |
     * (4)--------------------------(3)
     */
    public BoundingBox getEnvelope()
    {
      BoundingBox envelope;
      double* xloPos,yloPos;
      double* xhiPos,yhiPos;
      double* tmpPtr;
      int i;
      
      //step over all verticies, use pointers
      fixed (byte* beginCoord = &_buffer[beginOfCoords])
      {
        xloPos = (double*)beginCoord;
        yloPos = xloPos; yloPos++;

        xhiPos = xloPos;
        yhiPos = yloPos;

        tmpPtr = xloPos;

        //tmpPtr += 2; //point so second ordinate
        //i = numCoords - 1;

        i = _numCoords;
        while(i-- != 0)
        {
          if (*xloPos > *tmpPtr)
            xloPos = tmpPtr;
          if (*xhiPos < *tmpPtr)
            xhiPos = tmpPtr;

          tmpPtr++;

          if (*yloPos > *tmpPtr)
            yloPos = tmpPtr;
          if (*yhiPos < *tmpPtr)
            yhiPos = tmpPtr;

          tmpPtr++;
          if(dimension == Dimension.dim3D)
            tmpPtr++;
        }
      }

      envelope = new BoundingBox(*xloPos, *yloPos, *xhiPos, *yhiPos);

      return envelope;
    }




    //*******************************************************************
    //private interfaces
    
    
    /*private InternalCoordinate getCoord(int index)
    {
      InternalCoordinate tmpPoint = new InternalCoordinate(this, index);
      return tmpPoint;
    }*/

    
    
    
    private byte[] convert_3Dto2D(){
      int i;
      byte[] tmpCoords = new byte[(_numCoords * (sizeOfDouble * 2))];

      fixed (byte* beginCoord = &_buffer[beginOfCoords], beginTmpCoord = &tmpCoords[0])
      {
        //fixed (byte* beginTmpCoord = &tmpCoords[0])
        //{
          double* srcCoord;
          double* destCoord;
        
          i = 0;
          srcCoord = (double*)beginCoord;
          destCoord = (double*)beginTmpCoord;
          while (i++ < _numCoords)
          {
            *destCoord++ = *srcCoord++; //copy X
            *destCoord++ = *srcCoord++; //copy Y
            srcCoord++; //skip Z
          }
        }
      //}
      return tmpCoords;
    }



    /// <summary>
    /// converts current <c>CoordinateList</c> from 2D (x,y) to 3D (x,y,z)
    /// </summary>
    /// <param name="Z">default Z (height)</param>
    /// <returns>byte array with original coordinates in 3D</returns>
    private byte[] convert_2Dto3D(double Z)
    {
      int i;
      byte[] tmpCoords = new byte[(_numCoords * (sizeOfDouble * 3))];

      fixed (byte* beginCoord = &_buffer[beginOfCoords], beginTmpCoord = &tmpCoords[0])
      {
        //fixed (byte* beginTmpCoord = &tmpCoords[0])
        //{
          double* srcCoord;
          double* destCoord;

          i = 0;
          srcCoord = (double*)beginCoord;
          destCoord = (double*)beginTmpCoord;
          while (i++ < _numCoords)
          {
            *destCoord++ = *srcCoord++; //copy X
            *destCoord++ = *srcCoord++; //copy Y
            *destCoord++ = Z;
          }
        }
      //}
      return tmpCoords;
    }

  
  }

}
